package com.meatball.authorization.config

import com.meatball.authorization.data.dto.TokenDetailDto
import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component
import java.util.*
import java.util.function.Function

/**
 * TODO JwtService 类提供了 JWT (JSON Web Token) 的生成、解析和验证功能。
 *
 * @author 张翔宇
 * @since 2024年3月19日
 */
@Component
class JwtComponent {

    @Value("\${security.jwt.secret-key}")
    private lateinit var secretKey: String

    @Value("\${security.jwt.expiration}")
    private var jwtExpiration: Long = 0L

    @Value("\${security.jwt.refresh-token.expiration}")
    private var refreshExpiration: Long = 0L

    /**
     * TODO 从 JWT 中提取用户名
     *
     * @param token
     * @return
     */
    fun extractUsername(token: String): String {
        return extractClaim(token) { claims: Claims -> claims.subject }
    }

    /**
     * TODO 从 JWT 中提取特定的声明
     *
     * @param T
     * @param token
     * @param claimsResolver
     * @return
     */
    fun <T> extractClaim(token: String, claimsResolver: Function<Claims, T>): T {
        val claims = extractAllClaims(token)
        return claimsResolver.apply(claims)
    }

    /**
     * TODO 为用户生成一个新的 JWT，不包含额外的声明
     *
     * @param userDetails
     * @return
     */
    fun generateToken(userDetails: UserDetails): TokenDetailDto {
        return generateToken(HashMap(), userDetails)
    }

    /**
     * TODO 为用户生成一个新的 JWT，包含额外的声明
     *
     * @param extraClaims
     * @param userDetails
     * @return
     */
    fun generateToken(extraClaims: Map<String, Any>, userDetails: UserDetails): TokenDetailDto {
        return TokenDetailDto(
                token = buildToken(extraClaims, userDetails, jwtExpiration),
                expiration = jwtExpiration
        )
    }

    /**
     * TODO 为用户生成一个新的刷新令牌
     *
     * @param userDetails
     * @return
     */
    fun generateRefreshToken(userDetails: UserDetails): TokenDetailDto {
        return TokenDetailDto(
                token = buildToken(HashMap(), userDetails, refreshExpiration),
                expiration = refreshExpiration
        )
    }

    /**
     * TODO 构建 JWT，包含额外的声明、用户名、签发时间、过期时间，并签名
     *
     * @param extraClaims
     * @param userDetails
     * @param expiration
     * @return
     */
    private fun buildToken(extraClaims: Map<String, Any>, userDetails: UserDetails, expiration: Long): String {
        return Jwts.builder()
                .claims(extraClaims)
                .subject(userDetails.username)
                .issuedAt(Date(System.currentTimeMillis()))
                .expiration(Date(System.currentTimeMillis() + expiration))
                .signWith(Keys.hmacShaKeyFor(secretKey.toByteArray()))
                .compact()
    }

    /**
     * TODO  验证 JWT 是否有效，有效的条件是用户名匹配且令牌未过期
     *
     * @param token
     * @param userDetails
     * @return
     */
    fun isTokenValid(token: String, userDetails: UserDetails): Boolean {
        val username = extractUsername(token)
        return username == userDetails.username && !isTokenExpired(token)
    }

    /**
     * TODO 检查 JWT 是否已过期
     *
     * @param token
     * @return
     */
    private fun isTokenExpired(token: String): Boolean {
        return extractExpiration(token).before(Date())
    }

    /**
     * TODO 从 JWT 中提取过期时间
     *
     * @param token
     * @return
     */
    private fun extractExpiration(token: String): Date {
        return extractClaim(token) { claims: Claims -> claims.expiration }
    }

    /**
     * TODO 从 JWT 中提取所有的声明
     *
     * @param token
     * @return
     */
    private fun extractAllClaims(token: String): Claims {
        return Jwts.parser()
                .verifyWith(Keys.hmacShaKeyFor(secretKey.toByteArray()))
                .build()
                .parseSignedClaims(token)
                .payload
    }
}
